﻿/*
 *   StringFunctions.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitFox.Evaluator.Functions
{
    /// <summary>
    /// Built-in string functions for the evaluator.
    /// </summary>
    public static class StringFunctions
    {
        /// <summary>
        /// Removes all (leading and trailing) white spaces from a string.
        /// </summary>
        /// <param name="str">The string from which to remove white spaces</param>
        /// <returns>The passed string, without the spaces</returns>
        static public string AllTrim(string str)
        {
            if (str == null) throw new ArgumentNullException("str");
            return str.Trim();
        }

        /// <summary>
        /// Removes all trailing white spaces from a string.
        /// </summary>
        /// <param name="str">The string from which to remove white spaces</param>
        /// <returns>The passed string, without trailing spaces</returns>
        static public string Trim(string str)
        {
            if (str == null) throw new ArgumentNullException("str");
            return RTrim(str);
        }

        /// <summary>
        /// Removes all trailing white spaces from a string.
        /// </summary>
        /// <param name="src">The string from which to remove white spaces</param>
        /// <returns>The passed string, without trailing spaces</returns>
        static public string RTrim(string src)
        {
            if (src == null) throw new ArgumentNullException("src");
            for (int pos = src.Length - 1; pos >= 0; pos--)
                if (!char.IsWhiteSpace(src[pos]))
                    return src.Substring(0, pos + 1);
            return string.Empty;
        }

        /// <summary>
        /// Removes all leading white spaces from a string.
        /// </summary>
        /// <param name="src">The string from which to remove white spaces</param>
        /// <returns>The passed string, without leading spaces</returns>
        static public string LTrim(string src)
        {
            if (src == null) throw new ArgumentNullException("src");
            for (int pos = 0; pos < src.Length; pos++)
                if (!char.IsWhiteSpace(src[pos]))
                    return src.Substring(pos);
            return string.Empty;
        }

        /// <summary>
        /// Extract a portion at left of a string.
        /// </summary>
        /// <param name="src">The string from which to exctract left portion</param>
        /// <param name="num">The number of characters at left of string to return</param>
        /// <returns>The requested left portion of passed string</returns>
        static public string Left(string src, int num)
        {
            if (src == null) throw new ArgumentNullException("src");
            if (num < 0) num = 0;
            if (src.Length < num) num = src.Length;
            return src.Substring(0, num);
        }

        /// <summary>
        /// Extract a portion at right of a string.
        /// </summary>
        /// <param name="src">The string from which to extract right portion</param>
        /// <param name="num">The number of characters at right of string to return</param>
        /// <returns>The requested right portion of passed string</returns>
        static public string Right(string src, int num)
        {
            if (src == null) throw new ArgumentNullException("src");
            if (num < 0) num = 0;
            if (src.Length < num) num = src.Length;
            return src.Substring(src.Length - num);
        }

        /// <summary>
        /// Returns the ASCII code of the first character of passed string.
        /// </summary>
        /// <param name="src">The string which first character will be measured</param>
        /// <returns>The ASCII code of indicated character. 0 if the passed string is an empty one.</returns>
        static public int Asc(string src) 
        {
            if (src == null) throw new ArgumentNullException("src");
            if (src.Length < 1) return 0;
            return (int)src[0];
        }

        /// <summary>
        /// Search a string for another string embedded in it, skipping a given number of occurrences
        /// </summary>
        /// <param name="target">The string into which to search a patter</param>
        /// <param name="searched">The string to search inside <paramref name="target"/>.</param>
        /// <param name="num">The nth occurrence to find.</param>
        /// <returns>The offset position (starting with 1) where the pattern was found. <b>0</b> if the pattern is not found.</returns>
        static public int At(string target, string searched, int num)
        {
            if (target == null) throw new ArgumentNullException("target");
            if (searched == null) throw new ArgumentNullException("searched");
            if (num <= 0) throw new ArgumentException("'num' cannot be zero or negative");

            if (target.Length == 0 || searched.Length == 0) return 0;
            int startIndex = 0, index = -1;
            while (num-- > 0)
            {
                index = searched.IndexOf(target, startIndex, StringComparison.Ordinal);
                if (index < 0) return 0;
                startIndex = index + 1;
            }
            return index + 1;
        }

        /// <summary>
        /// Search a string inside another and return the position where the string is found
        /// </summary>
        /// <param name="target">The string to which to search for the pattern</param>
        /// <param name="searched">The pattern to find inside <paramref name="target"/>.</param>
        /// <returns>The position where <paramref name="searched"/> was found inside of <paramref name="target"/> 
        /// (starting in <b>1</b>), or zero if the pattern is not found.</returns>
        static public int At(string target, string searched)
        {
            return At(target, searched, 1);
        }

        /// <summary>
        /// Search a string for another string embedded in it, skipping a given number of occurrences. The search is
        /// performed in a case-insensitive way.
        /// </summary>
        /// <param name="target">The string into which to search a patter</param>
        /// <param name="searched">The string to search inside <paramref name="target"/>.</param>
        /// <param name="num">The nth occurrence to find.</param>
        /// <returns>The offset position (starting with 1) where the pattern was found. <b>0</b> if the pattern is not found.</returns>
        static public int Atc(string target, string searched, int num) 
        {
            return At(target.FoxToUpper(), searched.FoxToUpper(), num);
        }

        /// <summary>
        /// Search a string inside another and return the position where the string is found. The search is performed
        /// in a case-insensitive way.
        /// </summary>
        /// <param name="target">The string to which to search for the pattern</param>
        /// <param name="searched">The pattern to find inside <paramref name="target"/>.</param>
        /// <returns>The position where <paramref name="searched"/> was found inside of <paramref name="target"/> 
        /// (starting in <b>1</b>), or zero if the pattern is not found.</returns>
        static public int Atc(string target, string searched)
        {
            return Atc(target, searched, 1);
        }

        /// <summary>
        /// Function that returns the ASCII character equivalent to a given code.
        /// </summary>
        /// <param name="code">The ASCII code of the requested character.</param>
        /// <returns>The requested character, as a string</returns>
        static public string Chr(int code) 
        {
            if (code < 0 || code > 255) throw new ArgumentException("Invalid 'code' for CHR function");
            return ((char)code).FoxToString();
        }

        /// <summary>
        /// Test a given string to see if its first character is an alphabetic one.
        /// </summary>
        /// <param name="str">The string to test (only first character is tested)</param>
        /// <returns><b>true</b> if the first character is alphabetic character. <b>false</b> if the first character is
        /// not an alphabetic character, or if <paramref name="str"/> is an empty string.</returns>
        static public bool IsAlpha(string str) 
        {
            if (str == null) throw new ArgumentNullException("str");
            if (str.Length < 1) return false;
            return char.IsLetter(str[0]);
        }

        /// <summary>
        /// Test a given string to see if its first character is a numeric digit.
        /// </summary>
        /// <param name="str">The string to test (only first character is tested)</param>
        /// <returns><b>true</b> if the first character is numeric digit. <b>false</b> if the first character is
        /// not a numeric digit, or if <paramref name="str"/> is an empty string.</returns>
        static public bool IsDigit(string str) 
        {
            if (str == null) throw new ArgumentNullException("str");
            if (str.Length < 1) return false;
            return char.IsDigit(str[0]);
        }

        /// <summary>
        /// Test a given string to see if its first character is a alphanumeric lowercase character.
        /// </summary>
        /// <param name="src">The string to test (only first character is tested)</param>
        /// <returns><b>true</b> if the first character is an alphanumeric lowercase character. <b>false</b> if the first character is
        /// not an alphanumeric lowercase character, or if <paramref name="src"/> is an empty string.</returns>
        static public bool IsLower(string src) 
        {
            if (src == null) throw new ArgumentNullException("src");
            if (src.Length < 1) return false;
            char ch = src[0];
            return (ch >= 'a' && ch <= 'z');
        }

        /// <summary>
        /// Test a given string to see if its first character is a alphanumeric uppercase character.
        /// </summary>
        /// <param name="str">The string to test (only first character is tested)</param>
        /// <returns><b>true</b> if the first character is an alphanumeric uppercase character. <b>false</b> if the first character is
        /// not an alphanumeric uppercase character, or if <paramref name="str"/> is an empty string.</returns>
        static public bool IsUpper(string str)
        {
            if (str == null) throw new ArgumentNullException("str");
            if (str.Length < 1) return false;
            char ch = str[0];
            return (ch >= 'A' && ch <= 'Z');
        }

        /// <summary>
        /// Return the length of a string.
        /// </summary>
        /// <param name="str">The string which length is returned</param>
        /// <returns>The number of characters contained by <paramref name="str"/>.</returns>
        static public int Len(string str) 
        {
            if (str == null) throw new ArgumentNullException("str");
            return str.Length;
        }

        /// <summary>
        /// Determines whether or not a character expression is contained in another string.
        /// </summary>
        /// <param name="s1">
        /// <para>It is the character expression that can contain the wildcard characters * and ?.</para>
        /// <para>The question mark matches any single character in <paramref name="s2"/> and the asterisk matches any number of characters.</para>
        /// <para>You can mix any number of wildcard characters in any combination in <paramref name="s1"/></para>
        /// </param>
        /// <param name="s2">
        /// LIKE() compares <paramref name="s2"/> with <paramref name="s1"/>. <paramref name="s2"/> must match <paramref name="s1"/> letter
        /// for letter for LIKE() to return true.
        /// </param>
        /// <returns>
        /// LIKE() returns <b>true</b> if <paramref name="s1"/> matches <paramref name="s2"/>. LIKE() returns <b>false</b> if <paramref name="s1"/> doesn't match <paramref name="s2"/>.
        /// </returns>
        /// <remarks>
        /// LIKE() returns a logical value indicating whether a character expression <paramref name="s1"/>matches a second character 
        /// expression, <paramref name="s2"/>.
        /// All trailing blanks are removed both from <paramref name="s1"/> and <paramref name="s2"/> before comparison is performed.
        /// </remarks>
        static public bool Like(string s1, string s2) 
        {
            if (s1 == null) throw new ArgumentNullException("s1");
            if (s2 == null) throw new ArgumentNullException("s2");

            // work as if 'SET COMPATIBLE' were in ON
            s1 = s1.Trim();
            s2 = s2.Trim();

            int dstptr = 0;
            for (int srcptr = 0; srcptr < s1.Length; srcptr++)
            {
                switch (s1[srcptr])
                {
                    case '?':
                        if (dstptr >= s2.Length) return false;
                        dstptr++;
                        break;

                    case '*':
                        while (srcptr + 1 < s1.Length && s1[srcptr + 1] == '*')
                            srcptr++;
                        if (srcptr + 1 >= s1.Length) return true;
                        char expected = s1[srcptr + 1];
                        while (dstptr < s2.Length && s2[dstptr] != expected)
                            dstptr++;
                        if (dstptr >= s2.Length) return false;
                        break;

                    default:
                        if (dstptr >= s2.Length) return false;
                        if (s1[srcptr] != s2[dstptr++])
                            return false;
                        break;
                }
            }
            return (dstptr >= s2.Length);
        }

        /// <summary>
        /// Returns a lowercase version of passed string.
        /// </summary>
        /// <param name="str">The string which characters are converted to lowercase.</param>
        /// <returns><paramref name="str"/> converted to lowercase.</returns>
        static public string Lower(string str)
        {
            return str.FoxToLower();
        }

        /// <summary>
        /// Returns an uppercase version of passed string.
        /// </summary>
        /// <param name="str">The string which characters are converted to uppercase.</param>
        /// <returns><paramref name="str"/> converted to uppercase.</returns>
        static public string Upper(string str)
        {
            return str.FoxToUpper();
        }
        
        /// <summary>
        /// Returns the number of times a character expression occurs within another character expression.
        /// </summary>
        /// <param name="s1">A character expression for which to search.</param>
        /// <param name="s2">The second character expression<paramref name="s2"/> is searched for occurrences of the first character expression indicated in
        /// <paramref name="s1" />
        /// </param>
        /// <returns>The number of times <paramref name="s1"/> is found into <paramref name="s2"/>.</returns>
        static public int Occurs(string s1, string s2)
        {
            if (s1 == null) throw new ArgumentNullException("s1");
            if (s2 == null) throw new ArgumentNullException("s2");

            int res = 0;
            int dstPtr = 0, srcLen = s1.Length, dstLen = s2.Length;
            if (srcLen == 0) return 0;
            char first = s1[0];
            while (dstPtr + srcLen <= dstLen)
            {
                if (first == s2[dstPtr])
                {
                    bool found = true;
                    for (int i = 1; i < srcLen; i++)
                    {
                        if (s1[i] != s2[dstPtr + i])
                        {
                            found = false;
                            break;
                        }
                    }
                    if (found)
                    {
                        res++;
                        dstPtr += srcLen;
                    }
                }
                else
                    dstPtr++;
            }
            return res;
        }

        /// <summary>
        /// Helper method that do the PAD operation, at left, right or both sides of a string.
        /// </summary>
        /// <param name="src">The string to PAD</param>
        /// <param name="num">The expected final lenght of string <paramref name="src"/>, after padded</param>
        /// <param name="fillChar">The character with which to PAD</param>
        /// <param name="mode">
        /// 1= PAD at RIGHT
        /// 2= PAD at both sides
        /// 0= PAD at LEFT
        /// </param>
        /// <returns>The string passed at <paramref name="src"/>, after padded.</returns>
        static string PadX(string src, int num, string fillChar, int mode) 
        {
            // Validates arg
            if (fillChar.Length < 1)
                throw new ArgumentException("For 'PADx' functions, 'fill' exp must be of 1 or more chars");

            // Validates the number of chars to fill
            num -= src.Length;
            if (num <= 0) return src;
            
            // Determine how many chars to insert at left and right
            int nleft, nright;
            switch (mode)
            {
                case 1: nright = num; nleft = 0; break;
                case 2: nleft = num / 2; nright = num - nleft; break;
                default: nleft = num; nright = 0; break;
            }

            // Insert filler at left
            StringBuilder res = new StringBuilder();
            int fillPtr = 0;
            while (nleft-- > 0)
            {
                res.Append(fillChar[fillPtr++]);
                if (fillPtr >= fillChar.Length) fillPtr = 0;
            }
            
            // Append the string
            res.Append(src);
            
            // Append filler at right
            fillPtr = 0;
            while (nright-- > 0)
            {
                res.Append(fillChar[fillPtr++]);
                if (fillPtr >= fillChar.Length) fillPtr = 0;
            }
            return res.ToString();
        }

        /// <summary>
        /// Complete a string with spaces at left, to a given length.
        /// </summary>
        /// <param name="src">The string to pad at left</param>
        /// <param name="numPad">The expected final length of padded string</param>
        /// <returns>The padded string</returns>
        static public string Padl(string src, int numPad)
        {
            return PadX(src, numPad, " ", 0);
        }

        /// <summary>
        /// Complete a string at left with passed character, to a given length
        /// </summary>
        /// <param name="src">The string to pad.</param>
        /// <param name="num">The final length of string, after padded.</param>
        /// <param name="fillChar">The character with which to pad at left.</param>
        /// <returns>The padded string.</returns>
        static public string Padl(string src, int num, string fillChar)
        {
            return PadX(src, num, fillChar, 0);
        }

        /// <summary>
        /// Complete a string with spaces at right, to a given length.
        /// </summary>
        /// <param name="src">The string to pad with spaces at right.</param>
        /// <param name="num">The final expected length of string, after padded.</param>
        /// <returns>The padded string.</returns>
        static public string Padr(string src, int num)
        {
            return PadX(src, num, " ", 1);
        }

        /// <summary>
        /// Complete a string with a given character, at right, to a given length.
        /// </summary>
        /// <param name="src">The string to pad at right.</param>
        /// <param name="num">The final expected length of string, after padded.</param>
        /// <param name="fillChar">The character with which to pad the passed string.</param>
        /// <returns>The padded string.</returns>
        static public string Padr(string src, int num, string fillChar)
        {
            return PadX(src, num, fillChar, 1);
        }

        /// <summary>
        /// Centre and complete the passed string, with spaces at both sides, to a given final length.
        /// </summary>
        /// <param name="src">The string to pad at both sides.</param>
        /// <param name="num">The final length of string, after padded.</param>
        /// <returns>The padded string.</returns>
        static public string Padc(string src, int num)
        {
            return PadX(src, num, " ", 2);
        }

        /// <summary>
        /// Centre and complete the passed string with a given character, at both sides, to a given final length.
        /// </summary>
        /// <param name="src">The string to pad at both sides.</param>
        /// <param name="num">The final length of string, after padded.</param>
        /// <param name="fillChar">The character with which to fill the passed string, at both sides.</param>
        /// <returns>The padded string.</returns>
        static public string Padc(string src, int num, string fillChar)
        {
            return PadX(src, num, fillChar, 2);
        }

        /// <summary>
        /// Processes the passed string replacing the initial letter of each word with the uppercase version, and changing
        /// the remaining characters of each word to lowercase.
        /// </summary>
        /// <param name="src">The string to capitalize</param>
        /// <returns>The proper and capitalized string</returns>
        static public string Proper(string src) 
        {
            if (src == null) throw new ArgumentNullException("src");

            StringBuilder res = new StringBuilder();
            bool capitalize = true;
            foreach (char ch in src)
            {
                res.Append(capitalize? 
                    char.ToUpper(ch, CultureInfo.InvariantCulture): 
                    char.ToLower(ch, CultureInfo.InvariantCulture));
                capitalize = (ch == ' ' || ch == '\t');
            }

            return res.ToString();
        }

        /// <summary>
        /// Returns the beginning numeric position of the first occurrence of one string within another string,
        /// counting from the rightmost character.
        /// </summary>
        /// <param name="target">The string to find in <paramref name="searched"/>.</param>
        /// <param name="searched">The string into which to search <paramref name="target"/></param>
        /// <param name="num">The number of occurrence (counting from the last) to find.</param>
        /// <returns>0 if <paramref name="target"/> is not found in <paramref name="searched"/>, else, the position
        /// of the last occurrence of <paramref name="target"/> in <paramref name="searched"/>.</returns>
        /// <remarks>
        /// <para>RAT(), the reverse AT() function, searches a string, starting with the last character in it, for the 
        /// occurrence of another character expression.</para>
        /// <para>It then returns an integer corresponding to the position where the first character in the character
        /// expression is found.</para>
        /// <para>0 is returned if the character expression isn't found.</para>
        /// </remarks>
        static public int Rat(string target, string searched, int num)
        {
            if (target == null) throw new ArgumentNullException("target");
            if (searched == null) throw new ArgumentNullException("searched");
            if (num <= 0) throw new ArgumentException("'num' cannot be zero or negative");
            if (target.Length == 0 || searched.Length == 0) return 0;
            int startIndex = searched.Length - 1, index = -1;
            while (num-- > 0)
            {
                if (startIndex < 0) return 0;
                index = searched.LastIndexOf(target, startIndex, StringComparison.Ordinal);
                if (index < 0) return 0;
                startIndex = index - 1;
            }
            return index + 1;
        }

        /// <summary>
        /// Returns the beginning numeric position of the first occurrence of one string within another string,
        /// counting from the rightmost character.
        /// </summary>
        /// <param name="target">The string to find in <paramref name="searched"/>.</param>
        /// <param name="searched">The string into which to search <paramref name="target"/></param>
        /// <returns>0 if <paramref name="target"/> is not found in <paramref name="searched"/>, else, the position
        /// of the last occurrence of <paramref name="target"/> in <paramref name="searched"/>.</returns>
        /// <remarks>
        /// <para>RAT(), the reverse AT() function, searches a string, starting with the last character in it, for the 
        /// occurrence of another character expression.</para>
        /// <para>It then returns an integer corresponding to the position where the first character in the character
        /// expression is found.</para>
        /// <para>0 is returned if the character expression isn't found.</para>
        /// </remarks>
        static public int Rat(string target, string searched)
        {
            return Rat(target, searched, 1);
        }

        /// <summary>
        /// Returns a string that is <paramref name="str"/> repeated <paramref name="num"/> times.
        /// </summary>
        /// <param name="str">The string to repeat.</param>
        /// <param name="num">The number of times <paramref name="str"/> will be repeated.</param>
        /// <returns>The replicated string.</returns>
        static public string Replicate(string str, int num)
        {
            if (str == null) throw new ArgumentNullException("str");
            if (num < 0) throw new ArgumentException("'num' for replicate cannot be negative");
            if (num == 0 || str.Length == 0) return string.Empty;
            if (num == 1) return str;

            StringBuilder sb = new StringBuilder();
            while (num-- > 0)
                sb.Append(str);
            return sb.ToString();
        }

        /// <summary>
        /// Returns a string filled with <paramref name="num" /> white spaces.
        /// </summary>
        /// <param name="num">The number of spaces.</param>
        /// <returns>The filled string.</returns>
        static public string Space(int num) 
        {
            if (num < 0) throw new ArgumentException("'n' for 'space' cannot be negative");
            StringBuilder sb = new StringBuilder();
            while (num-- > 0)
                sb.Append(' ');
            return sb.ToString();
        }
        
        /// <summary>
        /// Converts a number into its string representation
        /// </summary>
        /// <param name="num">The number to convert</param>
        /// <returns>The number in <paramref name="num" /> converted to string</returns>
        static public string Str(decimal num) 
        {
            return Str(num, 10, 0);
        }

        /// <summary>
        /// Converts a number into its string representation.
        /// </summary>
        /// <param name="num">The number to convert</param>
        /// <param name="ndig">The number of digits in the integer part, expected.</param>
        /// <returns>The number in <paramref name="num" /> converted to string.
        /// 0 decimal places are returned.</returns>
        static public string Str(decimal num, int ndig)
        {
            return Str(num, ndig, 0);
        }

        /// <summary>
        /// Converts a number into its string representation.
        /// </summary>
        /// <param name="num">The number to convert</param>
        /// <param name="ndig">The number of digits in the integer part.</param>
        /// <param name="ndec">The number of decimal places to force in return.</param>
        /// <returns>The number in <paramref name="num" /> converted to string.</returns>
        static public string Str(decimal num, int ndig, int ndec)
        {
            string nstr = Decimal.Round(num, ndec).FoxToString();
            string[] parts = nstr.Split('.');       // Fox doesn't use culture
            if (parts[0].Length > ndig) return Replicate("*", ndig);

            string partdec = (parts.Length > 1) ? parts[1] : string.Empty;
            while (partdec.Length < ndec) partdec = partdec + "0";

            StringBuilder res = new StringBuilder();
            res.Append(parts[0]);
            ndig -= res.Length;
            if (ndig >= 2 && partdec.Length > 0)
            {
                res.Append('.'); ndig--;
                int decPtr = 0;
                while (ndig > 0 && decPtr < partdec.Length)
                {
                    res.Append(partdec[decPtr++]);
                    ndig--;
                }
            }

            while (ndig-- > 0) res.Insert(0, ' ');
            return res.ToString();
        }
        
        /// <summary>
        /// Replace part of string with the content of another string
        /// </summary>
        /// <param name="dst">The string that will be manipulated</param>
        /// <param name="start">The first position in <paramref name="dst"/>to discard</param>
        /// <param name="len">The number of characters in <paramref name="dst"/> to discard, starting in <paramref name="start"/></param>
        /// <param name="src">The string to put in place of discarded characters</param>
        /// <returns>The replaced string</returns>
        static public string Stuff(string dst, int start, int len, string src)
        {
            if (dst == null) throw new ArgumentNullException("dst");
            if (src == null) throw new ArgumentNullException("src");

            string res = string.Empty;
            if (start < 1) start = 1;
            if (len < 1) len = 1;
            start--;
            if (start >= dst.Length) return dst + src;
            if (dst.Length > start && start > 0) 
                res = dst.Substring(0, start);
            if (src.Length > 0)
                res += src;
            if (start + len < dst.Length) 
                res += dst.Substring(start + len);
            return res;
        }

        /// <summary>
        /// Return the right part of a string, starting in a given position.
        /// </summary>
        /// <param name="str">The string which right part will be returned.</param>
        /// <param name="start">The first position in <paramref name="str"/> (starting with 1) where the right part
        /// will be returned</param>
        /// <returns>The indicated right part of <paramref name="str"/>.</returns>
        static public string Substr(string str, int start)
        {
            return Substr(str, start, int.MaxValue);
        }

        /// <summary>
        /// Return a portion of passed string, starting at a given position and returning a given number of characters.
        /// </summary>
        /// <param name="str">The string for which the portion will be extracted</param>
        /// <param name="start">The first postion in <paramref name="str"/>(starting with 1) where the portion will be extracted.</param>
        /// <param name="count">The number of characters of <paramref name="str"/>, starting at <paramref name="start"/> that must be
        /// extracted.</param>
        /// <returns>The extracted portion</returns>
        static public string Substr(string str, int start, int count)
        {
            if (str == null) throw new ArgumentNullException("str");

            if (start < 0 || start >= str.Length) 
                throw new ArgumentException("Beyond string");
            if (start == 0) return string.Empty;
            return str.Substring(start-1, count);
        }
    
        /// <summary>
        /// Function that convert all characters from string <b>s</b> replacing them with characters from
        /// <b>list</b>, indexed by <b>index</b>.
        /// </summary>
        /// <param name="str">The string to convert</param>
        /// <param name="index">The list of characters to be converted.</param>
        /// <param name="list">The list of replacements to use</param>
        /// <returns>The converted strings.</returns>
        static public string ChrTran(string str, string index, string list)
        {
            if (str == null) throw new ArgumentNullException("str");
            if (index == null) throw new ArgumentNullException("index");
            if (list == null) throw new ArgumentNullException("list");

            StringBuilder sb = new StringBuilder();
            foreach (char ch in str)
            {
                int posi = index.IndexOf(ch);
                if (posi >= 0)
                {
                    sb.Append((posi < list.Length) ? (char)list[posi] : char.MinValue);
                }
                else
                    sb.Append(ch);
            }
            return sb.ToString();
        }

        /// <summary>
        /// Searches a string for occurrences of a second character expression or memo field, and then replaces each occurrence with a third character expression or memo field.
        /// </summary>
        /// <param name="s1">The string that's searched.</param>
        /// <param name="s2">The string that's searched for in <paramref name="s1"/>. The search is case-sensitive (lower-case and upper-case are respected).</param>
        /// <param name="s3">The search <paramref name="s2"/>is replaced in <paramref name="s1"/> by this string>.</param>
        /// <param name="n1">Specifies which occurrence of <paramref name="s3" /> is the first to be replaced.  For example, if <paramref name="n1" /> is 4,  
        /// replacement begins with the fourth occurrence of <paramref name="s2"/> in <paramref name="s1" />, counting from the left, 
        /// and the first three occurrences of <paramref name="s2" /> remain unchanged. </param>
        /// <param name="n2">Specifies the number of occurrences of <paramref name="s2"/> to replace.</param>
        /// <returns>The replaced string</returns>
        /// <remarks>
        /// <para>This function searches a string for occurrences of another string and replaces the occurrences 
        /// with a third character expression. The resulting character string is returned.</para>
        /// <para>You can also specify where the replacement begins and how many replacements are made.</para>
        /// </remarks>
        static public string StrTran(string s1, string s2, string s3, int n1, int n2) 
        {
            if (s1 == null) throw new ArgumentNullException("s1");
            if (s2 == null) throw new ArgumentNullException("s2");
            if (s3 == null) throw new ArgumentNullException("s3");

            StringBuilder res = new StringBuilder();

            // Find the position where start the replacements.
            int startIndex = 0, position = 0;
            while (n1-- > 0)
            {
                position = s1.IndexOf(s2, startIndex, StringComparison.Ordinal);
                if (position < 0) return s1;
                startIndex = position + s2.Length;
            }

            // We have the first replacement located.
            int startSubstring = 0;
            while (n2-- > 0)
            {
                res.Append(s1.Substring(startSubstring, position - startSubstring));
                res.Append(s3);
                startIndex = position + s2.Length;
                startSubstring = startIndex;
                position = s1.IndexOf(s2, startIndex, StringComparison.Ordinal);
                if (position < 0) break;
            }

            res.Append(s1.Substring(startSubstring));
            return res.ToString();
        }

        #region STRTRAN variants

        /// <summary>
        /// Searches a string for occurrences of a second character expression or memo field, and then replaces each occurrence with a third character expression or memo field.
        /// </summary>
        /// <param name="s1">The string that's searched.</param>
        /// <param name="s2">The string that's searched for in <paramref name="s1"/>. The search is case-sensitive (lower-case and upper-case are respected).</param>
        /// <param name="s3">The search <paramref name="s2"/>is replaced in <paramref name="s1"/> by this string>.</param>
        /// <param name="n1">Specifies which occurrence of <paramref name="s3" /> is the first to be replaced.  For example, if <paramref name="n1" /> is 4,  
        /// replacement begins with the fourth occurrence of <paramref name="s2"/> in <paramref name="s1" />, counting from the left, 
        /// and the first three occurrences of <paramref name="s2" /> remain unchanged. </param>
        /// <returns>The replaced string</returns>
        /// <remarks>
        /// <para>This function searches a string for occurrences of another string and replaces the occurrences 
        /// with a third character expression. The resulting character string is returned.</para>
        /// <para>You can also specify where the replacement begins and how many replacements are made.</para>
        /// </remarks>
        static public string StrTran(string s1, string s2, string s3, int n1)
        {
            return StrTran(s1, s2, s3, n1, int.MaxValue);
        }

        /// <summary>
        /// Searches a string for occurrences of a second character expression or memo field, and then replaces each occurrence with a third character expression or memo field.
        /// </summary>
        /// <param name="s1">The string that's searched.</param>
        /// <param name="s2">The string that's searched for in <paramref name="s1"/>. The search is case-sensitive (lower-case and upper-case are respected).</param>
        /// <param name="s3">The search <paramref name="s2"/>is replaced in <paramref name="s1"/> by this string>.</param>
        /// <returns>The replaced string</returns>
        /// <remarks>
        /// <para>This function searches a string for occurrences of another string and replaces the occurrences 
        /// with a third character expression. The resulting character string is returned.</para>
        /// <para>You can also specify where the replacement begins and how many replacements are made.</para>
        /// </remarks>
        static public string StrTran(string s1, string s2, string s3)
        {
            return StrTran(s1, s2, s3, 1, int.MaxValue);
        }

        /// <summary>
        /// Searches a string for occurrences of a second character expression or memo field, and then replaces each occurrence with an empty string.
        /// </summary>
        /// <param name="s1">The string that's searched.</param>
        /// <param name="s2">The string that's searched for in <paramref name="s1"/>. The search is case-sensitive (lower-case and upper-case are respected).</param>
        /// <remarks>
        /// <para>This function searches a string for occurrences of another string and replaces the occurrences 
        /// with a third character expression. The resulting character string is returned.</para>
        /// <para>You can also specify where the replacement begins and how many replacements are made.</para>
        /// </remarks>
        static public string StrTran(string s1, string s2)
        {
            return StrTran(s1, s2, string.Empty, 1, int.MaxValue);
        }

        #endregion
    }
}